﻿using System;
using System.Collections.Generic;

namespace Kang.ExtendMethod.Log
{
    public class LogFactory : IDisposable
    {
        private struct LoggerCacheKey : IEquatable<LoggerCacheKey>
        {
            public readonly string Name;

            public readonly Type ConcreteType;

            public LoggerCacheKey(string name, Type concreteType)
            {
                Name = name;
                ConcreteType = concreteType;
            }

            //
            // 摘要:
            //     Serves as a hash function for a particular type.
            //
            // 返回结果:
            //     A hash code for the current System.Object.
            public override int GetHashCode()
            {
                return ConcreteType.GetHashCode() ^ Name.GetHashCode();
            }

            //
            // 摘要:
            //     Determines if two objects are equal in value.
            //
            // 参数:
            //   obj:
            //     Other object to compare to.
            //
            // 返回结果:
            //     True if objects are equal, false otherwise.
            public override bool Equals(object obj)
            {
                object obj2;
                if ((obj2 = obj) is LoggerCacheKey)
                {
                    LoggerCacheKey other = (LoggerCacheKey)obj2;
                    return Equals(other);
                }

                return false;
            }

            //
            // 摘要:
            //     Determines if two objects of the same type are equal in value.
            //
            // 参数:
            //   other:
            //     Other object to compare to.
            //
            // 返回结果:
            //     True if objects are equal, false otherwise.
            public bool Equals(LoggerCacheKey other)
            {
                if (ConcreteType == other.ConcreteType)
                {
                    return string.Equals(other.Name, Name, StringComparison.Ordinal);
                }

                return false;
            }
        }

        //
        // 摘要:
        //     Logger cache.
        private class LoggerCache
        {
            private readonly Dictionary<LoggerCacheKey, WeakReference> _loggerCache = new Dictionary<LoggerCacheKey, WeakReference>();

            //
            // 摘要:
            //     Inserts or updates.
            //
            // 参数:
            //   cacheKey:
            //
            //   logger:
            public void InsertOrUpdate(LoggerCacheKey cacheKey, Logger logger)
            {
                _loggerCache[cacheKey] = new WeakReference(logger);
            }

            public Logger Retrieve(LoggerCacheKey cacheKey)
            {
                if (_loggerCache.TryGetValue(cacheKey, out var value))
                {
                    return value.Target as Logger;
                }

                return null;
            }

            public List<Logger> GetLoggers()
            {
                List<Logger> list = new List<Logger>(_loggerCache.Count);
                foreach (WeakReference value in _loggerCache.Values)
                {
                    Logger item;
                    if ((item = value.Target as Logger) != null)
                    {
                        list.Add(item);
                    }
                }

                return list;
            }

            public void Reset()
            {
                _loggerCache.Clear();
            }
        }

        internal readonly object _syncRoot = new object();
        private readonly LoggerCache _loggerCache = new LoggerCache();

        public Logger GetCurrentClassLogger()
        {
            string classFullName = GetClassFullName();
            return GetLogger(classFullName);
        }

        private string GetClassFullName()
        {
            int framesToSkip = 3;
            string className = string.Empty;
            var stackTrace = Environment.StackTrace;
            var stackTraceLines = stackTrace.Replace("\r", "").Split(new char[]{ '\n' }, StringSplitOptions.RemoveEmptyEntries);
            for (int i = 0; i < stackTraceLines.Length; ++i)
            {
                var callingClassAndMethod = stackTraceLines[i].Split(new[] { " ", "<>", "(", ")" }, StringSplitOptions.RemoveEmptyEntries)[1];
                int methodStartIndex = callingClassAndMethod.LastIndexOf(".", StringComparison.Ordinal);
                if (methodStartIndex > 0)
                {
                    var callingClass = callingClassAndMethod.Substring(0, methodStartIndex);
                    className = callingClass.TrimEnd('.');
                    if (!className.StartsWith("System.Environment", StringComparison.Ordinal) && framesToSkip != 0)
                    {
                        i += framesToSkip - 1;
                        framesToSkip = 0;
                        continue;
                    }
                    if (!className.StartsWith("System.", StringComparison.Ordinal))
                        break;
                }
            }
            return className;
        }

        public Logger GetLogger(string name)
        {
            return GetLoggerThreadSafe(name, typeof(Logger));
        }

        private Logger GetLoggerThreadSafe(string name, Type loggerType)
        {
            if (name == null)
            {
                throw new ArgumentNullException("name", "Name of logger cannot be null");
            }

            LoggerCacheKey cacheKey = new LoggerCacheKey(name, loggerType ?? typeof(Logger));
            lock (_syncRoot)
            {
                Logger logger = _loggerCache.Retrieve(cacheKey);
                if (logger != null)
                {
                    return logger;
                }
                cacheKey = new LoggerCacheKey(cacheKey.Name, typeof(Logger));
                logger = new Logger(name);
                _loggerCache.InsertOrUpdate(cacheKey, logger);
                return logger;
            }
        }

        public void Dispose()
        {
            GC.SuppressFinalize(this);
        }
    }
}
